﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.MSBuild;
using SharpAnalyzer.Calculators;
using SharpAnalyzer.Entities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Concurrent;

namespace SharpAnalyzer.Calculators
{
    internal class SolutionMetricsCalculator
    {
        public static CodeMetricsReport CalculateFromSolution(string file, bool ignoreGeneratedCode = false)
        {
            using (MSBuildWorkspace msBuildWorkspace = MSBuildWorkspace.Create())
            {
                msBuildWorkspace.LoadMetadataForReferencedProjects = true;
                Solution solution = msBuildWorkspace.OpenSolutionAsync(file).Result;

                CodeMetricsReport retval = Calculate(solution, solution.Projects, ignoreGeneratedCode);
                retval.InputFile = file;
                return retval;
            }
        }

        public static CodeMetricsReport CalculateFromProject(string file, bool ignoreGeneratedCode = false)
        {
            using (MSBuildWorkspace msBuildWorkspace = MSBuildWorkspace.Create())
            {
                Microsoft.CodeAnalysis.Project project = msBuildWorkspace.OpenProjectAsync(file).Result;

                CodeMetricsReport retval = Calculate(null, new Microsoft.CodeAnalysis.Project[1] { project }, ignoreGeneratedCode);
                retval.InputFile = file;
                return retval;
            }
        }

        private static CodeMetricsReport Calculate(Solution solution, IEnumerable<Microsoft.CodeAnalysis.Project> projects, bool ignoreGeneratedCode = false)
        {
            BlockingCollection<Entities.Project> modules = new BlockingCollection<Entities.Project>();
            // Parallel.ForEach since each project is calculated within itself
            Parallel.ForEach<Microsoft.CodeAnalysis.Project>(projects, (project =>
            {
                Entities.Project moduleMetric = ProjectMetricsCalculator.Calculate(solution, project, ignoreGeneratedCode);
                if (moduleMetric == null)
                    return;
                modules.Add(moduleMetric);
            }));

            IEnumerable<string> sourceCC = CalculateClassCoupling(modules);
            CodeMetricsReport codeMetricsReport = new CodeMetricsReport()
            {
                Version = "1.0",
                Projects = modules.ToList(),
                Metrics = new List<Metric>()
                {
                    new Metric()
                    {
                        Name = MetricNames.MaintainabilityIndex,
                        Value = CalculateMaintainabilityIndex(modules),
                    },
                    new Metric()
                    {
                        Name = MetricNames.CyclomaticComplexity,
                        Value = CalculateCyclomaticComplexity(modules),
                    },
                    new ClassCouplingMetricResult()
                    {
                        Name = MetricNames.ClassCoupling,
                        Value = sourceCC.Count(),
                        Types = sourceCC
                    },
                    new Metric()
                    {
                        Name = MetricNames.DepthOfInheritance,
                        Value = CalculateDepthOfInheritance(modules),
                    },
                    new Metric()
                    {
                        Name = MetricNames.LinesOfCode,
                        Value = CalculateLinesOfCode(modules),
                    },
                    new Metric()
                    {
                        Name = MetricNames.NumberOfNamespaces,
                        Value = CalculateNumberOfNamespaces(modules),
                    },
                    new Metric()
                    {
                        Name = MetricNames.NumberOfTypes,
                        Value = CalculateNumberOfTypes(modules),
                    },
                    new Metric()
                    {
                        Name = MetricNames.NumberOfMethods,
                        Value = CalculateNumberOfMethods(modules),
                    },
                },
            };
            return codeMetricsReport;
        }

        #region Accumulate Results
        private static int CalculateNumberOfTypes(IEnumerable<Entities.Project> modules)
        {
            var source = from module in modules
                         from metric in module.Metrics
                         where metric.Name == MetricNames.NumberOfTypes
                         select (int)metric.Value;
            return source.Sum();
        }

        private static int CalculateNumberOfMethods(IEnumerable<Entities.Project> modules)
        {
            var source = from module in modules
                         from metric in module.Metrics
                         where metric.Name == MetricNames.NumberOfMethods
                         select (int)metric.Value;
            return source.Sum();
        }

        private static int CalculateNumberOfNamespaces(IEnumerable<Entities.Project> modules)
        {
            var source = from module in modules
                         select module.Namespaces.Count();
            return source.Sum();
        }

        private static IEnumerable<string> CalculateClassCoupling(IEnumerable<Entities.Project> modules)
        {
            var source = from module in modules
                         from metric in module.Metrics.OfType<ClassCouplingMetricResult>()
                         from type in metric.Types
                         orderby type
                         select type;

            return source.Distinct();
        }

        private static double CalculateMaintainabilityIndex(IEnumerable<Entities.Project> modules)
        {
            var source = from module in modules
                         from metric in module.Metrics
                         where metric.Name == MetricNames.MaintainabilityIndex
                         select (double)metric.Value;
            if (source.Any())
                return Math.Round(source.Average());
            return 100.0;
        }

        private static int CalculateCyclomaticComplexity(IEnumerable<Entities.Project> modules)
        {
            var source = from module in modules
                         from metric in module.Metrics
                         where metric.Name == MetricNames.CyclomaticComplexity
                         select (int)metric.Value;
            return source.Sum();
        }

        private static int CalculateDepthOfInheritance(IEnumerable<Entities.Project> modules)
        {
            var source = from module in modules
                         from metric in module.Metrics
                         where metric.Name == MetricNames.DepthOfInheritance
                         select (int)metric.Value;
            if (source.Any())
                return source.Max();

            return 0;
        }

        private static int CalculateLinesOfCode(IEnumerable<Entities.Project> modules)
        {
            var source = from module in modules
                         from metric in module.Metrics
                         where metric.Name == MetricNames.LinesOfCode
                         select (int)metric.Value;
            return source.Sum();
        }
        #endregion
    }
}
